package com.liuyi.security;

import com.fmy.mcsecurity.commons.unusual.McSecurityException;
import com.fmy.mcsecurity.config.McSecurityConfig;
import com.fmy.mcsecurity.config.builders.HttpMcSecurity;
import com.fmy.mcsecurity.login.hander.LoginHandler;
import com.fmy.mcsecurity.token.model.SecurityToken;
import com.liuyi.security.vote.JdbcVote;
import com.liuyi.tools.kits.RequestKit;
import com.liuyi.tools.kits.ToolKit;
import com.liuyi.tools.web.resp.Response;
import com.liuyi.tools.web.resp.ResponseCall;
import com.liuyi.tools.web.unusual.BizEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.vote.UnanimousBased;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.expression.WebExpressionVoter;
import org.springframework.web.cors.CorsUtils;

import java.util.Arrays;

/**
 * @author Mr.fmy
 * @version 1.0.0
 * @ClassName SecurityPublicConfig
 * @Description TODO Security 抽象类配置
 * <p>
 * 基本已经配好了，只继承，交给Spring也能用，唯一需要额外配置的其实是 登录可访问路径和不登录可访问路径
 * </p>
 * @createTime 2020/4/15 11:14
 */
public abstract class SecurityAbstractConfig extends McSecurityConfig {

    @Autowired
    private UserDetailsService userDetailsService;

    public static final String TOKEN_BEARER_KEY = "UUID ", TOKEN_KEY = "L-TOKEN";

    @Override
    protected void configure(HttpMcSecurity security) {
        security.setUserPassParamsMatcher(getLoginUri())
                .setTokenContextUrl(getAuthenticatedUri())
                .setBearer(TOKEN_BEARER_KEY).setTokenKey(TOKEN_KEY);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //关闭cors以及csrf
        http.cors().and().csrf().disable()
                //登录路径配置
                .formLogin().loginPage(getLoginUri());

        http.authorizeRequests()
                //处理跨域请求中的Preflight请求
                .requestMatchers(CorsUtils::isPreFlightRequest).permitAll();

        http.exceptionHandling()
                //配置权限不足异常处理
                .accessDeniedHandler(accessDeniedHandler())
                //配置匿名访问异常处理，当token不存在或过期也会触发该方法
                .authenticationEntryPoint(authenticationEntryPoint());


    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(authenticationProvider());
    }

    /**
     * 自定义认证器
     */
    protected DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider provider = new CustDaoAuthenticationProvider();
        //不要封装异常
        provider.setHideUserNotFoundExceptions(false);
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(new PasswordEncoder() {
            @Override
            public String encode(CharSequence charSequence) {
                return ToolKit.md5Salt("", charSequence.toString());
            }

            @Override
            public boolean matches(CharSequence charSequence, String s) {
                return s.equals(encode(charSequence));
            }
        });
        return provider;
    }

    /**
     * 自定义继承一下 DaoAuthenticationProvider，要不 密码盐值没办法得到，很难受
     */
    public class CustDaoAuthenticationProvider extends DaoAuthenticationProvider {

        protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
            if (authentication.getCredentials() == null) {
                throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
            } else {
                String presentedPassword = authentication.getCredentials().toString();
                if (userDetails instanceof LiuYiUser) {
                    presentedPassword = ToolKit.saltPass(((LiuYiUser) userDetails).getSalt(), presentedPassword);
                }
                if (!super.getPasswordEncoder().matches(presentedPassword, userDetails.getPassword())) {
                    throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
                }
            }
        }
    }

    protected AccessDecisionManager accessDecisionManager(JdbcVote.UriAuthority uriAuthority) {
        return new UnanimousBased(Arrays.asList(new WebExpressionVoter(), new JdbcVote(uriAuthority)));
    }

    protected abstract String getLoginUri();

    protected abstract String[] getAuthenticatedUri();

    /**
     * 自定义 {@link LoginHandler} 接口
     */
    public interface LoginHandlerImpl extends LoginHandler<Response> {
        /**
         * 登录失败回调、这里可以做处理
         */
        default Response resultFail(Exception e) {
            if (e instanceof McSecurityException) {
                return ResponseCall.error(((McSecurityException) e).getiEnum().getCode(), e.getMessage());
            }
            return ResponseCall.error(BizEnum.LOGIN_FALL, e.getMessage());
        }


        default Response resultSuccess(SecurityToken token) {
            return ResponseCall.success("登录成功", token);
        }

        @Override
        default void resultPrefix() {

        }
    }

    /**
     * 匿名访问异常处理
     */
    protected AuthenticationEntryPoint authenticationEntryPoint() {
        return (request, response, e) -> RequestKit.responseWriter(response, HttpStatus.UNAUTHORIZED, ResponseCall.error(BizEnum.LOGIN_ENTRY_POINT, e.getMessage()));
    }

    /**
     * 无权限访问处理
     */
    protected AccessDeniedHandler accessDeniedHandler() {
        return (request, response, e) -> RequestKit.responseWriter(response, HttpStatus.FORBIDDEN, ResponseCall.error(BizEnum.LOGIN_ACCESS_DENIED));
    }
}
